#include "ExceptionSafeThread.hpp"

#include "../System.hpp"

/********************************************//**
 * \brief Creates an instance of the class.
 *
 ***********************************************/
se::System::ExceptionSafeThread::ExceptionSafeThread():
	m_terminatedCorrectly(false),
	m_isRunning(false),
	m_freeSelfOnExit(false),
	m_logAnything(true),
	m_logOnCancellation(true),
	m_thread(NULL),
	m_threadFunctor(){
}

/********************************************//**
 * \brief Creates an instance of the class.
 *
 * \param t The functor that will be ran.
 ***********************************************/
se::System::ExceptionSafeThread::ExceptionSafeThread(boost::function<void()> t):
	m_terminatedCorrectly(false),
	m_isRunning(false),
	m_freeSelfOnExit(false),
	m_logAnything(true),
	m_logOnCancellation(true),
	m_thread(NULL),
	m_threadFunctor(){
	SetFunctor(t);
}

/********************************************//**
 * \brief Destroy the instance of the class.
 *
 ***********************************************/
se::System::ExceptionSafeThread::~ExceptionSafeThread(){
	if(m_isRunning && !m_freeSelfOnExit){
		if(m_logAnything)
			SE_LOGC(
				EXCEPTION_UNHANDLED,
				"An ExceptionSafeThread has been destroyed while the thread was still running.");
		exit(-1);
	}
	if(m_thread) delete m_thread;
}

/********************************************//**
 * \brief Sets the thread functor.
 *
 * Can only be set once.
 * \param t The functor that will be ran.
 ***********************************************/
void se::System::ExceptionSafeThread::SetFunctor(boost::function<void()> t){
	if(m_threadFunctor){
		if(m_logAnything)
			SE_LOGC(
				EXCEPTION_UNHANDLED,
				"An ExceptionSafeThread was changed. It shall be initialised only once.");
		exit(-1);
	}
	m_threadFunctor = t;
}

/********************************************//**
 * \brief Sets wether logging will be used internally.
 *
 * Can be set while the thread is running, but no
 * garanty that it will be taken in account.
 * \param logAnything Wether the trace will be used at all.
 * Note that it overrides every other parameters.
 * \param logOnCancellation Whether something will be
 * written in the log if the thread is cacelled.
 ***********************************************/
void se::System::ExceptionSafeThread::SetLogging(bool logAnything, bool logOnCancellation){
	m_logAnything = logAnything;
	m_logOnCancellation = logOnCancellation;
}

/********************************************//**
 * \brief Calls the functor in another thread.
 *
 * Note that you can't call this function twice,
 * the object representing only one thread.
 *
 * In theory, you may restart the thread if its 
 * previous execution is over.
 ***********************************************/
void se::System::ExceptionSafeThread::Start(){
	if(m_isRunning){
		if(m_logAnything)
			SE_LOGC(
				EXCEPTION_UNHANDLED,
				"An ExceptionSafeThread was started twice.");
		exit(-1);
	}
	m_thread = 
		new boost::thread(
			boost::bind(
				&se::System::ExceptionSafeThread::handleExceptions,
				this));
	m_isRunning = true;
}

/********************************************//**
 * \brief Returns wether the thread is currently running.
 ***********************************************/
bool se::System::ExceptionSafeThread::IsRunning(){
	return m_isRunning;
}

/********************************************//**
 * \brief Wait for the thread to terminate.
 * 
 * During this time, the father thread is stopped at the
 * function call.
 * \return true if everything was fine.
 ***********************************************/
bool se::System::ExceptionSafeThread::Join(){
	if(!m_isRunning){
		if(m_logAnything)
			SE_LOGC(
				EXCEPTION_UNHANDLED,
				"A non running ExceptionSafeThread cannot be joined.");
		exit(-1);
	}
	m_thread->join();
	m_isRunning = false;
	return m_terminatedCorrectly;
}

/********************************************//**
 * \brief Wait for the thread to terminate.
 * 
 * This function is blocking, waiting for the thread
 * to actually terminate.
 * 
 * Please note that a terminated thread is considered
 * as termiated correctly.
 * 
 * See documentation of std::thread::cancel() for
 * more informations.
 ***********************************************/
void se::System::ExceptionSafeThread::Terminate(){
	if(!m_isRunning){
		if(m_logAnything)
			SE_LOGC(
				EXCEPTION_UNHANDLED,
				"A non running ExceptionSafeThread cannot be terminated.");
		exit(-1);
	}
	m_thread->interrupt();
	m_thread->join();
}

void se::System::ExceptionSafeThread::handleExceptions(){
	try{
		m_threadFunctor();
		m_terminatedCorrectly = true;
	}catch(boost::thread_interrupted){
		if(m_logAnything&&m_logOnCancellation){
			if(m_logAnything)
				SE_LOGC(
					INFO,
					"A thread was cancelled  : "
					<<boost::this_thread::get_id()
					<<".")
		}
		m_terminatedCorrectly = true;
	}catch(std::exception e){
		if(m_logAnything)
			SE_LOGC(
				EXCEPTION_UNHANDLED,
				"An exception was thrown in thread "
				<<boost::this_thread::get_id()
				<<". What():"<<e.what())
	}catch(...){
		if(m_logAnything)
			SE_LOGC(
				EXCEPTION_UNHANDLED,
				"An exception was thrown in thread "
				<<boost::this_thread::get_id()
				<<". This exception does not derivates from std::exception.")
	}
}
